﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace mForum.Infrastructure.NHibernate
{
    using mForum.Core.Common;
    using mForum.Core.Repository;
    using mForum.Domain.Repository;
    using mForum.Domain.DomainObject;
    using mForum.Core.Specification;

    public class BaseRepository<TEntity> : IRepository<TEntity>, IDisposable where TEntity : class, IEntity
    {
        private DatabaseContext _databaseContext;

        public BaseRepository(IDatabaseFactory databaseFactory)
        {
            Check.Argument.IsNotNull(databaseFactory, "databaseFactory");

            DatabaseFactory = databaseFactory;
        }

        protected IDatabaseFactory DatabaseFactory
        {
            get;
            private set;
        }

        protected DatabaseContext DatabaseContext
        {
            get
            {
                return _databaseContext ?? (_databaseContext = DatabaseFactory.Get());
            }
        }

        public void Dispose()
        {
            if (DatabaseContext != null)
                GC.SuppressFinalize(DatabaseContext);

            if (DatabaseFactory != null)
                GC.SuppressFinalize(DatabaseFactory);
        }

        public IEnumerable<TEntity> GetBySpec(ISpecification<TEntity> specification)
        {
            Check.Argument.IsNotNull(specification, "specification");

            IEnumerable<TEntity> result = DatabaseContext.CreateQuery<TEntity>().Where(specification.SatisfiedBy());

            return result;
        }

        public virtual void Add(TEntity entity)
        {
            Check.Argument.IsNotNull(entity, "entity");

            DatabaseContext.Save<TEntity>(entity);
        }

        public virtual void Delete(Guid id)
        {
            Check.Argument.IsNotNull(id, "id");

            TEntity entity = GetById(id);

            if (entity != null)
                DatabaseContext.Delete<TEntity>(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            Check.Argument.IsNotNull(entity, "entity");

            DatabaseContext.Delete<TEntity>(entity);
        }

        public virtual TEntity GetById(Guid id)
        {
            return DatabaseContext.CreateQuery<TEntity>().SingleOrDefault<TEntity>(x => x.Id == id);
        }

        public virtual IEnumerable<TEntity> All()
        {
            return DatabaseContext.CreateQuery<TEntity>();
        }

        public IEnumerable<TEntity> GetPagedElements<S>(int pageIndex, int pageSize, Expression<Func<TEntity, S>> orderExpression, bool ascending)
        {
            Check.Argument.IsNotNegativeOrZero(pageIndex, "pageIndex");
            Check.Argument.IsNotNegative(pageSize, "pageSize");
            Check.Argument.IsNotNull(orderExpression, "orderExpression");

            IQueryable<TEntity> objectQuery = DatabaseContext.CreateQuery<TEntity>();

            return (ascending) ? objectQuery.OrderBy(orderExpression)
                                            .Skip((pageIndex - 1) * pageSize)
                                            .Take(pageSize)
                                            .ToList()

                                : objectQuery.OrderByDescending(orderExpression)
                                            .Skip((pageIndex - 1) * pageSize)
                                            .Take(pageSize)
                                            .ToList();

        }

        public IEnumerable<TEntity> GetPagedElements<S>(int pageIndex, int pageSize, Expression<Func<TEntity, S>> orderExpression, ISpecification<TEntity> specification, bool ascending)
        {
            Check.Argument.IsNotNegativeOrZero(pageIndex, "pageIndex");
            Check.Argument.IsNotNegative(pageSize, "pageSize");
            Check.Argument.IsNotNull(orderExpression, "orderExpression");
            Check.Argument.IsNotNull(specification, "specification");

            IQueryable<TEntity> objectQuery = DatabaseContext.CreateQuery<TEntity>();

            return (ascending) ? objectQuery.Where(specification.SatisfiedBy())
                                            .OrderBy(orderExpression)
                                            .Skip((pageIndex - 1) * pageSize)
                                            .Take(pageSize)
                                            .ToList()

                                : objectQuery.Where(specification.SatisfiedBy())
                                            .OrderByDescending(orderExpression)
                                            .Skip((pageIndex - 1) * pageSize)
                                            .Take(pageSize)
                                            .ToList();

        }
    }
}
